#include "Student.h"

using namespace std;

// Initialize the schedule
void Student::initialSchedule() {
    for (size_t i = 0; i < schedule.size(); i++) {
        for (size_t  j = 0; j < schedule[i].size(); j++) {
            schedule[i][j] = "";
        }
    }
    // Add the weekdays header
    // Columns
    array<string, 7> weekDays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };
    for (size_t i = 1; i < schedule[0].size(); i++) {
        schedule[0][i] = weekDays[i - 1];
    }
    // Add the time slots header
    // Rows
    for (size_t i = 1; i < schedule.size(); i++) {
        schedule[i][0] = to_string(i);
    }
}

// Select courses
void Student::selectCourse(CourseLib& courseLibrary) {
    // Print the current course schedule
    printSchedule();
    
    bool isChooseAgain = false;
    //bool isAdd = true;

    do {
        // Get course number
        cout << "Please enter the course number to select courses(enter '-1' ends the input)" << endl;
        int courseNumber;
        cin >> courseNumber;
        if (cin.fail()) {
            cout << "Invalid input! Please enter a number." << endl;
            cin.clear();
            while (cin.get() != '\n') {}
            continue;
        }

        if (courseNumber == -1) break;

        // Find the course
        Course currentSelectedCourse;
        bool found = false;
        for (const Course& course : courseLibrary.courses) {
            if (course.courseId == to_string(courseNumber)) {
                currentSelectedCourse = course;
                found = true;
                break;
            }
        }

        if (!found) {
            cout << "This course does not exist." << endl;
            continue;
        }

        if (isCourseConflict(currentSelectedCourse)) {
            cout << "The course conflicts with an already selected course." << endl;
            continue;
        }

        creditSum.push_back(currentSelectedCourse.credit);
        selectedCourses.push_back(currentSelectedCourse);
        
        // Convert string to number
        int selectedCourseId = 999; 
        istringstream ss(currentSelectedCourse.courseId);
        ss >> selectedCourseId;
        selectedCourseIds.insert(selectedCourseId);
        addCourseToSchedule(currentSelectedCourse);
        printSchedule();
        float totalCredit = calculateTotalCredits();
        if (totalCredit >= 20 && isCreditSufficient(totalCredit)) {
            break;
        } else {
            cout << "You need to select more courses to meet the minimum credit requirement of 30 credits." << endl;
            isChooseAgain = true;
        }

    } while (isChooseAgain);
}

// Helper function: Parse class time string and return a vector of time slots
vector<std::tuple<int, int, int>> parseClassTime(const string& classTime) {
    vector<std::tuple<int, int, int>> timeRanges;
    istringstream stream(classTime);
    string segment;

    while (stream >> segment) { // Split by spaces
        if (segment.size() == 3) {
            int day = segment[0] - '0';       // Weekday
            int start = segment[1] - '0';     // Start time
            int end = segment[2] - '0';       // End time
            if (end == 0) end = 10;           // Special case: End time of 0 means the 10th period
            timeRanges.emplace_back(day, start, end);
        }
    }

    return timeRanges;
}

// Helper function: Check if two time slots overlap
bool isTimeConflict(int day1, int start1, int end1, int day2, int start2, int end2) {
    return day1 == day2 && !(end1 < start2 || end2 < start1);
}

// Check if the new course conflicts with already selected courses
bool Student::isCourseConflict(const Course& newCourse) {
    vector<std::tuple<int, int, int>> newCourseTimes = parseClassTime(newCourse.classTime);

    for (const Course& course : selectedCourses) {
        vector<std::tuple<int, int, int>> existingCourseTimes = parseClassTime(course.classTime);

        for (const auto& newTime : newCourseTimes) {
            int day1 = std::get<0>(newTime);
            int start1 = std::get<1>(newTime);
            int end1 = std::get<2>(newTime);

            for (const auto& existingTime : existingCourseTimes) {
                int day2 = std::get<0>(existingTime);
                int start2 = std::get<1>(existingTime);
                int end2 = std::get<2>(existingTime);

                if (isTimeConflict(day1, start1, end1, day2, start2, end2)) {
                    return true; // Conflict
                }
            }
        }
    }
    return false;
}

// Add a course to the schedule
void Student::addCourseToSchedule(const Course& currentCourse) {
    vector<std::tuple<int, int, int>> timeRanges = parseClassTime(currentCourse.classTime);

    for (const auto& timeRange : timeRanges) {
        int week = std::get<0>(timeRange);
        int startTime = std::get<1>(timeRange);
        int endTime = std::get<2>(timeRange);

        for (int i = startTime; i <= endTime; ++i) {
            schedule[i][week] = currentCourse.courseName;
        }
    }
}

void Student::removeCourseFromSchedule(const Course& courseToRemove) {
    vector<std::tuple<int, int, int>> timeRanges = parseClassTime(courseToRemove.classTime);

    for (const auto& timeRange : timeRanges) {
        int week = std::get<0>(timeRange);
        int startTime = std::get<1>(timeRange);
        int endTime = std::get<2>(timeRange);

        for (int i = startTime; i <= endTime; ++i) {
            schedule[i][week] = "";
        }
    }
}

void Student::printSchedule() {
    if(this->isCreditSufficient(this->calculateTotalCredits())){
        cout << "Your credits are enough!" << " Credits: " << this->calculateTotalCredits() << endl;
    } else {
        cout << "You still need "<< 20- (this->calculateTotalCredits())<< " credits."<< endl;
    }

    // Calculate the maximum width for each row
    int maxWidth = 0;
    for (const auto& day : schedule) {
        int dayMaxWidth = 0;
        for (const auto& period : day) {
            dayMaxWidth = max(dayMaxWidth, static_cast<int>(period.length()));
        }
        maxWidth = max(maxWidth, dayMaxWidth);
    }

    for (int i = 0; i < schedule.size(); i++) {
        for (int j = 0; j < schedule[i].size(); j++) {
            cout << setw(maxWidth) << schedule[i][j] << " ";
        }
        cout << endl;
    }
}

void Student::dropCourse(CourseLib& courseLibrary) {
    // Print the courses currently selected by the user
    cout << "The present courses you have selected are: " << endl;
    for (const auto& course : selectedCourses) {
        cout << course.courseId << ": ";
        cout << course.courseName << " " << endl;
    }
    cout << endl;

    // Ask the user which course they want to withdraw from
    cout << "What class do you want to withdraw from? (enter '-1' ends the input)" << endl;
    // Record the course to be withdrawn
    string courseNumber;

    // Record the list of courses the user wants to drop
    vector<Course> droppedCourses;

    do {
        cin >> courseNumber;
        if (courseNumber == "-1") break;

        Course currentDropedCourse;
        bool found = false;
        for (auto it = selectedCourses.begin(); it != selectedCourses.end(); ++it) {
            if (it->courseId == courseNumber) {
                currentDropedCourse = *it;
                // Remove using iterator
                selectedCourses.erase(it);
                found = true;
                break;
            }
        }

        if (!found) {
            cout << "Course not found!" << endl;
            continue;
        }

        // Remove the credits of the dropped course from creditSum
        creditSum.erase(remove(creditSum.begin(), creditSum.end(), currentDropedCourse.credit), creditSum.end());
        
        // Convert string to number
        int currentDropedCourseId = 999;
        istringstream ss(currentDropedCourse.courseId);
        ss >> currentDropedCourseId;
        selectedCourseIds.erase(currentDropedCourseId);

        droppedCourses.push_back(currentDropedCourse);
        removeCourseFromSchedule(currentDropedCourse);

    } while (courseNumber != "-1");

    float totalCredit = calculateTotalCredits();
    if (isCreditSufficient(totalCredit)) {
        printSchedule();
    } else {
        selectCourse(courseLibrary);  // Allow the user to add more courses
    }
}

float Student::calculateTotalCredits() {
    return accumulate(creditSum.begin(), creditSum.end(), 0.0f);
}

bool Student::isCreditSufficient(float sumCredit) {
    return sumCredit >= 20;
}

